@link-assistant/web-capture 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bin/web-capture.js +2 -5
  3. package/package.json +22 -11
  4. package/src/archive.js +0 -1
  5. package/src/binary-download.js +0 -1
  6. package/src/docx.js +13 -10
  7. package/src/fetch.js +0 -1
  8. package/src/gdocs-images.js +0 -1
  9. package/src/gdocs.js +0 -1
  10. package/src/github.js +0 -1
  11. package/src/lib.js +0 -1
  12. package/src/markdown.js +0 -1
  13. package/src/shared-dialog.js +0 -1
  14. package/src/stream.js +0 -1
  15. package/src/transport.js +23 -5
  16. package/src/txt.js +0 -1
  17. package/.changeset/README.md +0 -8
  18. package/.changeset/config.json +0 -11
  19. package/.dockerignore +0 -6
  20. package/.jscpd.json +0 -19
  21. package/.lenv +0 -8
  22. package/.prettierignore +0 -8
  23. package/.prettierrc +0 -10
  24. package/Dockerfile +0 -52
  25. package/docker-compose.yml +0 -11
  26. package/eslint.config.js +0 -112
  27. package/examples/js/capture_cache.js +0 -12
  28. package/examples/js/engine_comparison.js +0 -39
  29. package/examples/js/html_download.js +0 -23
  30. package/examples/js/image_download.js +0 -36
  31. package/examples/js/markdown_download.js +0 -23
  32. package/examples/js/playwright_example.js +0 -23
  33. package/examples/python/capture_client.py +0 -20
  34. package/examples/python/html_download.py +0 -3
  35. package/examples/python/image_download.py +0 -16
  36. package/examples/python/markdown_download.py +0 -3
  37. package/examples/python/playwright_example.py +0 -22
  38. package/jest.config.mjs +0 -17
  39. package/tests/ci/dependency-freshness.test.js +0 -74
  40. package/tests/ci/detect-code-changes.test.js +0 -187
  41. package/tests/ci/docker-install.test.js +0 -31
  42. package/tests/ci/rust-dockerfile.test.js +0 -54
  43. package/tests/ci/workflow-policy.test.js +0 -79
  44. package/tests/e2e/docker.test.js +0 -186
  45. package/tests/e2e/process.test.js +0 -215
  46. package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
  47. package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
  48. package/tests/fixtures/deepwiki/index.md +0 -1500
  49. package/tests/fixtures/deepwiki/part-1.md +0 -776
  50. package/tests/fixtures/habr-articles.js +0 -45
  51. package/tests/fixtures/heading-numbering.html +0 -19
  52. package/tests/fixtures/list-item-with-br.html +0 -8
  53. package/tests/fixtures/ol-continuous-numbering.html +0 -12
  54. package/tests/fixtures/paragraph-vs-line-break.html +0 -11
  55. package/tests/helpers/nock.js +0 -10
  56. package/tests/helpers/screenshot.js +0 -10
  57. package/tests/helpers/search-fixture.cjs +0 -17
  58. package/tests/helpers/temporary-directory.js +0 -12
  59. package/tests/integration/api-endpoints.test.js +0 -418
  60. package/tests/integration/archive-default-layout.test.js +0 -37
  61. package/tests/integration/browser-engines.test.js +0 -80
  62. package/tests/integration/browser-packaged-path.test.js +0 -45
  63. package/tests/integration/deepwiki-download.test.js +0 -144
  64. package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
  65. package/tests/integration/formalai-contract.test.js +0 -214
  66. package/tests/integration/gdocs-public-doc.test.js +0 -315
  67. package/tests/integration/github-readme.test.js +0 -133
  68. package/tests/integration/google-drive-image.test.js +0 -246
  69. package/tests/integration/habr-article.test.js +0 -337
  70. package/tests/integration/search-endpoint.test.js +0 -88
  71. package/tests/integration/stackoverflow-download.test.js +0 -173
  72. package/tests/integration/wikipedia-download.test.js +0 -134
  73. package/tests/jest.setup.mjs +0 -7
  74. package/tests/mock/index.test.js +0 -225
  75. package/tests/unit/animation.test.js +0 -33
  76. package/tests/unit/batch.test.js +0 -114
  77. package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
  78. package/tests/unit/browser-launch-options.test.js +0 -42
  79. package/tests/unit/browser.test.js +0 -172
  80. package/tests/unit/cache.test.js +0 -182
  81. package/tests/unit/cli.test.js +0 -399
  82. package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
  83. package/tests/unit/convertRelativeUrls.test.js +0 -34
  84. package/tests/unit/extract-images.test.js +0 -259
  85. package/tests/unit/fetch-html.test.js +0 -54
  86. package/tests/unit/figures.test.js +0 -74
  87. package/tests/unit/gdocs-export-headers.test.js +0 -28
  88. package/tests/unit/gdocs-preprocess.test.js +0 -36
  89. package/tests/unit/gdocs.test.js +0 -1490
  90. package/tests/unit/github.test.js +0 -156
  91. package/tests/unit/heading-image.test.js +0 -49
  92. package/tests/unit/heading-numbering.test.js +0 -28
  93. package/tests/unit/html2md-br-in-list-item.test.js +0 -23
  94. package/tests/unit/html2md-ol-numbering.test.js +0 -20
  95. package/tests/unit/html2md.test.js +0 -695
  96. package/tests/unit/image-mode-defaults.test.js +0 -54
  97. package/tests/unit/kreuzberg.test.js +0 -249
  98. package/tests/unit/latex.test.js +0 -123
  99. package/tests/unit/localize-images.test.js +0 -114
  100. package/tests/unit/markdown.test.js +0 -36
  101. package/tests/unit/metadata.test.js +0 -140
  102. package/tests/unit/package-runtime-dependencies.test.js +0 -74
  103. package/tests/unit/paragraph-vs-line-break.test.js +0 -56
  104. package/tests/unit/postprocess.test.js +0 -110
  105. package/tests/unit/proxy-headers.test.js +0 -100
  106. package/tests/unit/publish-verification.test.js +0 -54
  107. package/tests/unit/retry.test.js +0 -111
  108. package/tests/unit/search.test.js +0 -339
  109. package/tests/unit/shared-dialog.test.js +0 -173
  110. package/tests/unit/stackoverflow.test.js +0 -32
  111. package/tests/unit/stream.test.js +0 -83
  112. package/tests/unit/verify.test.js +0 -182
  113. package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
  114. package/tests/xpaste/xpaste.e2e.test.js +0 -36
  115. package/tests/xpaste/xpaste.integration.test.js +0 -41
  116. package/tests/xpaste/xpaste.mock.test.js +0 -261
  117. package/tests/xpaste/xpaste.unit.test.js +0 -119
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @link-assistant/web-capture
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 724c1a9: Fix CI/CD false positives, false negatives, and warnings (#177):
8
+
9
+ - Restore the `browser-commander` 0.10.0 pin required by open blocker #160 and repair the npm lock so `npm ci` installs again after the Dependabot update.
10
+ - Use Node's built-in `fetch` instead of `node-fetch`, removing the deprecated `node-domexception` package from the install. Transport diagnostics now include the underlying network error (for example `fetch failed: connect ECONNREFUSED ...`) and no longer report server-side network failures as possible CORS blocks.
11
+ - Load the `docx` library only when a DOCX is generated. Its bundled browser polyfills read `globalThis.localStorage` on import, so on Node 25+ every process that imported the server printed "ExperimentalWarning: localStorage is not available because --localstorage-file was not provided".
12
+ - Publish only the runtime files (`bin/`, `src/`, `README.md`, `CHANGELOG.md`) instead of the tests and fixtures, normalize `bin` and `repository` so `npm publish` no longer auto-corrects them, and install the pre-commit hook from the repository root instead of printing ".git can't be found" on every install.
13
+
3
14
  ## 2.0.0
4
15
 
5
16
  ### Major Changes
@@ -1110,11 +1110,9 @@ async function captureUrl(url, options) {
1110
1110
  await import('../src/lib.js');
1111
1111
  const { retry } = await import('../src/retry.js');
1112
1112
  const cheerio = await import('cheerio');
1113
- const nodeFetch = await import('node-fetch');
1114
1113
 
1114
+ // retry() defaults: 3 retries from a 1000ms base delay.
1115
1115
  const html = await retry(() => fetchHtml(absoluteUrl), {
1116
- retries: 3,
1117
- baseDelay: 1000,
1118
1116
  onRetry: (err, attempt) => {
1119
1117
  console.error(`Retry ${attempt} fetching page: ${err.message}`);
1120
1118
  },
@@ -1207,8 +1205,7 @@ async function captureUrl(url, options) {
1207
1205
  if (imageMap.size > 0) {
1208
1206
  for (const [imgUrl, localPath] of imageMap) {
1209
1207
  try {
1210
- const fetchFn = nodeFetch.default || nodeFetch;
1211
- const resp = await retry(() => fetchFn(imgUrl), {
1208
+ const resp = await retry(() => fetch(imgUrl), {
1212
1209
  retries: 2,
1213
1210
  baseDelay: 500,
1214
1211
  });
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@link-assistant/web-capture",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "CLI and microservice to render web pages as HTML, Markdown, or PNG",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "web-capture": "./bin/web-capture.js"
7
+ "web-capture": "bin/web-capture.js"
8
8
  },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "CHANGELOG.md",
13
+ "README.md"
14
+ ],
9
15
  "scripts": {
10
16
  "start": "docker compose up -d",
11
17
  "dev": "node --watch bin/web-capture.js --serve",
@@ -19,13 +25,13 @@
19
25
  "examples:python": "uv run --project .. --locked python examples/python/html_download.py && uv run --project .. --locked python examples/python/markdown_download.py && uv run --project .. --locked python examples/python/image_download.py",
20
26
  "examples:javascript": "node examples/js/html_download.js && node examples/js/markdown_download.js && node examples/js/image_download.js",
21
27
  "examples": "npm run build && npm run examples:javascript && npm run examples:python",
22
- "lint": "eslint .",
28
+ "lint": "node ../scripts/lint.mjs",
23
29
  "lint:fix": "eslint . --fix",
24
30
  "format": "prettier --write .",
25
31
  "format:check": "prettier --check .",
26
32
  "check:duplication": "jscpd . --baseline-from-ref origin/main --fail-on-new-clones --fail-on-empty",
27
33
  "check": "npm run lint && npm run format:check && npm run check:duplication",
28
- "prepare": "husky || true",
34
+ "prepare": "node ../scripts/install-git-hooks.mjs",
29
35
  "changeset": "changeset",
30
36
  "changeset:version": "node ../scripts/changeset-version.mjs",
31
37
  "changeset:publish": "changeset publish",
@@ -42,15 +48,15 @@
42
48
  "iconv-lite": "^0.7.3",
43
49
  "lino-arguments": "^0.3.0",
44
50
  "log-lazy": "^1.0.4",
45
- "node-fetch": "^3.3.2",
46
51
  "playwright": "^1.64.0",
47
- "puppeteer": "^25.12.0",
48
- "puppeteer-core": "^25.12.0",
52
+ "puppeteer": "^25.13.0",
53
+ "puppeteer-core": "^25.13.0",
49
54
  "turndown": "^7.2.4",
50
55
  "turndown-plugin-gfm": "^1.0.2"
51
56
  },
52
57
  "devDependencies": {
53
58
  "@changesets/cli": "^3.0.3",
59
+ "@eslint/js": "^10.0.1",
54
60
  "@types/archiver": "^8.0.0",
55
61
  "eslint": "^10.12.0",
56
62
  "eslint-config-prettier": "^10.1.8",
@@ -64,8 +70,7 @@
64
70
  "nock": "^15.0.1",
65
71
  "prettier": "^3.9.9",
66
72
  "supertest": "^7.3.1",
67
- "unzipper": "^0.12.5",
68
- "@eslint/js": "^10.0.1"
73
+ "unzipper": "^0.12.5"
69
74
  },
70
75
  "type": "module",
71
76
  "keywords": [
@@ -77,14 +82,14 @@
77
82
  "license": "Unlicense",
78
83
  "repository": {
79
84
  "type": "git",
80
- "url": "https://github.com/link-assistant/web-capture"
85
+ "url": "git+https://github.com/link-assistant/web-capture.git"
81
86
  },
82
87
  "engines": {
83
88
  "node": ">=26.10.0"
84
89
  },
85
90
  "lint-staged": {
86
91
  "*.{js,mjs,cjs}": [
87
- "eslint --fix --max-warnings 0",
92
+ "eslint --fix --no-warn-ignored",
88
93
  "prettier --write",
89
94
  "prettier --check"
90
95
  ],
@@ -102,6 +107,12 @@
102
107
  "overrides": {
103
108
  "@istanbuljs/load-nyc-config": {
104
109
  "js-yaml": "^4.1.1"
110
+ },
111
+ "cheerio": {
112
+ "encoding-sniffer": "^1.0.2"
113
+ },
114
+ "babel-plugin-istanbul": {
115
+ "test-exclude": "^8.0.0"
105
116
  }
106
117
  },
107
118
  "packageManager": "npm@12.2.0",
package/src/archive.js CHANGED
@@ -16,7 +16,6 @@
16
16
  */
17
17
 
18
18
  import { ZipArchive } from 'archiver';
19
- import fetch from 'node-fetch';
20
19
  import * as cheerio from 'cheerio';
21
20
  import { URL } from 'url';
22
21
  import {
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { retry } from './retry.js';
3
2
 
4
3
  // Shared image-download policy; keep HTTP errors distinct from network retries.
package/src/docx.js CHANGED
@@ -9,18 +9,8 @@
9
9
  * engine - 'puppeteer' or 'playwright' (only used for browser rendering fallback)
10
10
  */
11
11
 
12
- import fetch from 'node-fetch';
13
12
  import * as cheerio from 'cheerio';
14
13
  import { URL } from 'url';
15
- import {
16
- Document,
17
- Packer,
18
- Paragraph,
19
- TextRun,
20
- HeadingLevel,
21
- ImageRun,
22
- ExternalHyperlink,
23
- } from 'docx';
24
14
  import { fetchHtml, convertRelativeUrls } from './lib.js';
25
15
 
26
16
  export async function docxHandler(req, res) {
@@ -35,6 +25,19 @@ export async function docxHandler(req, res) {
35
25
  const absHtml = convertRelativeUrls(html, absoluteUrl);
36
26
  const $ = cheerio.load(absHtml);
37
27
 
28
+ // Loaded on demand: docx bundles a browser util-deprecate that reads
29
+ // globalThis.localStorage on import, which makes Node >=25 print an
30
+ // ExperimentalWarning for every process that merely imports the app.
31
+ const {
32
+ Document,
33
+ Packer,
34
+ Paragraph,
35
+ TextRun,
36
+ HeadingLevel,
37
+ ImageRun,
38
+ ExternalHyperlink,
39
+ } = await import('docx');
40
+
38
41
  // Remove unwanted elements
39
42
  $('style, script, noscript, nav, footer, header').remove();
40
43
 
package/src/fetch.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { convertGoogleDriveUrl } from './lib.js';
3
2
  import { copyProxyResponseHeaders } from './proxy-headers.js';
4
3
 
@@ -5,7 +5,6 @@
5
5
  // the archive is self-contained. This module downloads the images and
6
6
  // rewrites the captured output accordingly.
7
7
 
8
- import fetch from 'node-fetch';
9
8
  import { URL } from 'url';
10
9
 
11
10
  const DEFAULT_USER_AGENT =
package/src/gdocs.js CHANGED
@@ -7,7 +7,6 @@
7
7
  // https://docs.googleapis.com/v1/documents/{DOCUMENT_ID}
8
8
  // Browser-model capture loads /edit and extracts DOCS_modelChunk data.
9
9
 
10
- import fetch from 'node-fetch';
11
10
  import * as he from 'he';
12
11
  import * as cheerio from 'cheerio';
13
12
  import { convertHtmlToMarkdown } from './lib.js';
package/src/github.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { URL } from 'node:url';
3
2
 
4
3
  const GITHUB_API_BASE = 'https://api.github.com';
package/src/lib.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-useless-escape */
2
2
  // Common logic for the web-capture microservice
3
- import fetch from 'node-fetch';
4
3
  import * as cheerio from 'cheerio';
5
4
  import TurndownService from 'turndown';
6
5
  import iconv from 'iconv-lite';
package/src/markdown.js CHANGED
@@ -18,7 +18,6 @@ import { applyImageMode } from './extract-images.js';
18
18
  import { createBrowser, getBrowserEngine } from './browser.js';
19
19
  import { retry } from './retry.js';
20
20
  import { ZipArchive } from 'archiver';
21
- import fetch from 'node-fetch';
22
21
  import * as cheerio from 'cheerio';
23
22
 
24
23
  const INLINE_MARKDOWN_LINE_LIMIT = 1500;
@@ -8,7 +8,6 @@
8
8
  * unsupported diagnostic instead of guessed content.
9
9
  */
10
10
 
11
- import fetch from 'node-fetch';
12
11
  import * as he from 'he';
13
12
  import { URL } from 'node:url';
14
13
 
package/src/stream.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { convertGoogleDriveUrl } from './lib.js';
3
2
  import { copyProxyResponseHeaders } from './proxy-headers.js';
4
3
 
package/src/transport.js CHANGED
@@ -1,5 +1,3 @@
1
- import fetch from 'node-fetch';
2
-
3
1
  export const RECEIPT_HEADERS = [
4
2
  'cache-control',
5
3
  'content-encoding',
@@ -22,22 +20,42 @@ function selectedHeaders(headers) {
22
20
  return selected;
23
21
  }
24
22
 
23
+ const TIMEOUT_CAUSE_CODES = new Set([
24
+ 'ETIMEDOUT',
25
+ 'UND_ERR_CONNECT_TIMEOUT',
26
+ 'UND_ERR_HEADERS_TIMEOUT',
27
+ 'UND_ERR_BODY_TIMEOUT',
28
+ ]);
29
+
25
30
  function classifyTransportError(error, signal) {
26
31
  if (signal?.aborted || error?.name === 'AbortError') {
27
32
  return 'cancelled';
28
33
  }
29
- if (error?.name === 'TimeoutError' || error?.type === 'request-timeout') {
34
+ if (
35
+ error?.name === 'TimeoutError' ||
36
+ TIMEOUT_CAUSE_CODES.has(error?.cause?.code)
37
+ ) {
30
38
  return 'timeout';
31
39
  }
32
- if (error instanceof TypeError) {
40
+ // Node's built-in fetch rejects every network failure with
41
+ // TypeError('fetch failed') and the real reason in `cause`. Browsers give a
42
+ // cause-less TypeError for CORS and network errors alike.
43
+ if (error?.name === 'TypeError' && !error.cause) {
33
44
  return 'cors_or_transport';
34
45
  }
35
46
  return 'transport';
36
47
  }
37
48
 
49
+ /** Message of `error` including its `cause`, e.g. "fetch failed: getaddrinfo ENOTFOUND host". */
50
+ export function describeTransportError(error) {
51
+ const message = error?.message || String(error);
52
+ const cause = error?.cause?.message;
53
+ return cause && !message.includes(cause) ? `${message}: ${cause}` : message;
54
+ }
55
+
38
56
  export class CaptureTransportError extends Error {
39
57
  constructor(error, url, signal) {
40
- super(error?.message || String(error));
58
+ super(describeTransportError(error));
41
59
  this.name = 'CaptureTransportError';
42
60
  this.cause = error;
43
61
  this.diagnostics = {
package/src/txt.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { URL } from 'node:url';
3
2
  import {
4
3
  fetchHtml,
@@ -1,8 +0,0 @@
1
- # Changesets
2
-
3
- Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
- with multi-package repos, or single-package repos to help you version and publish your code. You can
5
- find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
-
7
- We have a quick list of common questions to get you started engaging with this project in
8
- [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "../node_modules/@changesets/config/schema.json",
3
- "changelog": "@changesets/cli/changelog",
4
- "commit": false,
5
- "fixed": [],
6
- "linked": [],
7
- "access": "public",
8
- "baseBranch": "main",
9
- "updateInternalDependencies": "patch",
10
- "ignore": []
11
- }
package/.dockerignore DELETED
@@ -1,6 +0,0 @@
1
- node_modules
2
- reports
3
- coverage
4
- *.log
5
- .captures
6
- capture-replay.json
package/.jscpd.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "minTokens": 30,
3
- "minLines": 5,
4
- "ignore": [
5
- "**/node_modules/**",
6
- "**/build/**",
7
- "**/dist/**",
8
- "**/*.min.js",
9
- "**/coverage/**",
10
- "**/.changeset/**",
11
- "**/package-lock.json",
12
- "**/pnpm-lock.yaml",
13
- "**/yarn.lock",
14
- "**/reports/**"
15
- ],
16
- "reporters": ["console", "html"],
17
- "output": "./reports/jscpd",
18
- "mode": "weak"
19
- }
package/.lenv DELETED
@@ -1,8 +0,0 @@
1
- # web-capture default configuration
2
- # This file uses Links Notation format (key: value)
3
-
4
- # Server port
5
- PORT: 3000
6
-
7
- # Browser engine (puppeteer or playwright)
8
- BROWSER_ENGINE: puppeteer
package/.prettierignore DELETED
@@ -1,8 +0,0 @@
1
- node_modules
2
- coverage
3
- dist
4
- *.min.js
5
- package-lock.json
6
- .eslintcache
7
- CLAUDE.md
8
- tests/fixtures/deepwiki/*.md
package/.prettierrc DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "semi": true,
3
- "trailingComma": "es5",
4
- "singleQuote": true,
5
- "printWidth": 80,
6
- "tabWidth": 2,
7
- "useTabs": false,
8
- "arrowParens": "always",
9
- "endOfLine": "lf"
10
- }
package/Dockerfile DELETED
@@ -1,52 +0,0 @@
1
- FROM node:26.10.0-trixie
2
-
3
- WORKDIR /app
4
-
5
- # Install dependencies for headless Chrome (used by both Puppeteer and Playwright)
6
- # Retry loop handles transient network errors from Debian mirrors in CI
7
- RUN apt-get update && \
8
- for i in 1 2 3; do \
9
- apt-get install -y \
10
- chromium \
11
- fonts-liberation \
12
- libasound2t64 \
13
- libatk-bridge2.0-0t64 \
14
- libatk1.0-0t64 \
15
- libatspi2.0-0t64 \
16
- libcups2t64 \
17
- libdbus-1-3 \
18
- libdrm2 \
19
- libgbm1 \
20
- libgtk-3-0t64 \
21
- libnspr4 \
22
- libnss3 \
23
- libxcomposite1 \
24
- libxdamage1 \
25
- libxfixes3 \
26
- libxrandr2 \
27
- libxshmfence1 \
28
- xdg-utils \
29
- --no-install-recommends && break || \
30
- { if [ "$i" -eq 3 ]; then exit 1; fi; \
31
- echo "apt-get install attempt $i failed, retrying..."; \
32
- apt-get update && sleep 5; }; \
33
- done && \
34
- rm -rf /var/lib/apt/lists/*
35
-
36
- # Set Puppeteer environment variables
37
- ENV PUPPETEER_SKIP_DOWNLOAD=true
38
- ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
39
- ENV PUPPETEER_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage"
40
-
41
- # Set Playwright environment variables
42
- ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
43
- ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
44
-
45
- COPY package.json package-lock.json ./
46
- RUN npm install --global npm@12.2.0 && npm ci
47
-
48
- COPY . .
49
-
50
- EXPOSE 3000
51
-
52
- ENTRYPOINT ["node", "bin/web-capture.js", "--serve"]
@@ -1,11 +0,0 @@
1
- services:
2
- web-capture:
3
- build:
4
- context: .
5
- dockerfile: Dockerfile
6
- ports:
7
- - '3000:3000'
8
- extra_hosts:
9
- - 'host.docker.internal:host-gateway'
10
- environment:
11
- - PORT=3000
package/eslint.config.js DELETED
@@ -1,112 +0,0 @@
1
- import js from '@eslint/js';
2
- import prettierConfig from 'eslint-config-prettier';
3
- import prettierPlugin from 'eslint-plugin-prettier';
4
-
5
- export default [
6
- js.configs.recommended,
7
- prettierConfig,
8
- {
9
- files: ['**/*.js', '**/*.mjs'],
10
- plugins: {
11
- prettier: prettierPlugin,
12
- },
13
- languageOptions: {
14
- ecmaVersion: 'latest',
15
- sourceType: 'module',
16
- globals: {
17
- // Node.js globals
18
- console: 'readonly',
19
- process: 'readonly',
20
- Buffer: 'readonly',
21
- __dirname: 'readonly',
22
- __filename: 'readonly',
23
- global: 'readonly',
24
- setTimeout: 'readonly',
25
- clearTimeout: 'readonly',
26
- setInterval: 'readonly',
27
- clearInterval: 'readonly',
28
- // Node.js 18+ globals
29
- fetch: 'readonly',
30
- // Runtime-specific globals
31
- Bun: 'readonly',
32
- Deno: 'readonly',
33
- },
34
- },
35
- rules: {
36
- // Prettier integration
37
- 'prettier/prettier': 'error',
38
-
39
- // Code quality rules
40
- 'no-unused-vars': 'error',
41
- 'no-console': 'off', // Allow console in this project
42
- 'no-debugger': 'error',
43
-
44
- // Best practices
45
- eqeqeq: ['error', 'always'],
46
- curly: ['error', 'all'],
47
- 'no-var': 'error',
48
- 'prefer-const': 'error',
49
- 'prefer-arrow-callback': 'error',
50
- 'no-duplicate-imports': 'error',
51
-
52
- // ES6+ features
53
- 'arrow-body-style': ['error', 'as-needed'],
54
- 'object-shorthand': ['error', 'always'],
55
- 'prefer-template': 'error',
56
-
57
- // Async/await
58
- 'no-async-promise-executor': 'error',
59
- 'require-await': 'warn',
60
-
61
- // Comments and documentation
62
- 'spaced-comment': ['error', 'always', { markers: ['/'] }],
63
-
64
- // Complexity rules - reasonable thresholds for maintainability
65
- complexity: ['warn', 15], // Cyclomatic complexity - allow more complex logic than strict 8
66
- 'max-depth': ['warn', 5], // Maximum nesting depth - slightly more lenient than strict 4
67
- 'max-lines-per-function': [
68
- 'warn',
69
- {
70
- max: 150, // More reasonable than strict 50 lines per function
71
- skipBlankLines: true,
72
- skipComments: true,
73
- },
74
- ],
75
- 'max-params': ['warn', 6], // Maximum function parameters - slightly more lenient than strict 5
76
- 'max-statements': ['warn', 60], // Maximum statements per function - reasonable limit for orchestration functions
77
- 'max-lines': ['error', 1500], // Maximum lines per file - counts all lines including blank lines and comments
78
- },
79
- },
80
- {
81
- // Test files have different requirements
82
- files: ['tests/**/*.js', '**/*.test.js', '**/*.test.mjs'],
83
- languageOptions: {
84
- globals: {
85
- // Jest globals
86
- describe: 'readonly',
87
- it: 'readonly',
88
- test: 'readonly',
89
- expect: 'readonly',
90
- beforeAll: 'readonly',
91
- afterAll: 'readonly',
92
- beforeEach: 'readonly',
93
- afterEach: 'readonly',
94
- jest: 'readonly',
95
- },
96
- },
97
- rules: {
98
- 'require-await': 'off', // Async functions without await are common in tests
99
- 'max-lines-per-function': 'off', // Test files can have long test functions
100
- 'max-statements': 'off', // Test files can have many statements
101
- },
102
- },
103
- {
104
- ignores: [
105
- 'node_modules/**',
106
- 'coverage/**',
107
- 'dist/**',
108
- '*.min.js',
109
- '.eslintcache',
110
- ],
111
- },
112
- ];
@@ -1,12 +0,0 @@
1
- import { CaptureStore, CachedTransport } from '../../src/cache.js';
2
- import { fetchTransport } from '../../src/transport.js';
3
-
4
- const store = new CaptureStore('.captures');
5
- const request = { url: 'https://example.com', method: 'GET', headers: {} };
6
- const online = new CachedTransport(fetchTransport, store);
7
- const first = await online.capture(request);
8
- const offline = new CachedTransport(fetchTransport, store, { offline: true });
9
- const replay = await offline.capture(request);
10
- console.log(replay.sha256, replay.cached, replay.stale);
11
- console.log('Exact replay:', first.receipt.body.equals(replay.receipt.body));
12
- console.log('Portable fixture:', await store.export(request));
@@ -1,39 +0,0 @@
1
- import fetch from 'node-fetch';
2
- import fs from 'fs';
3
-
4
- const baseUrl = 'http://localhost:3000';
5
- const targetUrl = 'https://example.com';
6
-
7
- console.log('Comparing Puppeteer and Playwright engines...\n');
8
-
9
- // Fetch using Puppeteer
10
- console.log('1. Fetching HTML using Puppeteer (default)...');
11
- const startPuppeteer = Date.now();
12
- const puppeteerResponse = await fetch(
13
- `${baseUrl}/html?url=${encodeURIComponent(targetUrl)}`
14
- );
15
- const puppeteerHtml = await puppeteerResponse.text();
16
- const puppeteerTime = Date.now() - startPuppeteer;
17
- fs.writeFileSync('output/puppeteer_comparison.html', puppeteerHtml);
18
- console.log(` Time: ${puppeteerTime}ms`);
19
- console.log(` Size: ${puppeteerHtml.length} bytes`);
20
-
21
- // Fetch using Playwright
22
- console.log('\n2. Fetching HTML using Playwright...');
23
- const startPlaywright = Date.now();
24
- const playwrightResponse = await fetch(
25
- `${baseUrl}/html?url=${encodeURIComponent(targetUrl)}&engine=playwright`
26
- );
27
- const playwrightHtml = await playwrightResponse.text();
28
- const playwrightTime = Date.now() - startPlaywright;
29
- fs.writeFileSync('output/playwright_comparison.html', playwrightHtml);
30
- console.log(` Time: ${playwrightTime}ms`);
31
- console.log(` Size: ${playwrightHtml.length} bytes`);
32
-
33
- // Summary
34
- console.log('\n=== Summary ===');
35
- console.log(`Puppeteer: ${puppeteerTime}ms, ${puppeteerHtml.length} bytes`);
36
- console.log(`Playwright: ${playwrightTime}ms, ${playwrightHtml.length} bytes`);
37
- console.log(`Difference: ${Math.abs(puppeteerTime - playwrightTime)}ms`);
38
-
39
- console.log('\nResults saved to output/ directory.');
@@ -1,23 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- const url = process.argv[2] || 'https://example.com';
7
- const endpoint = `http://localhost:3000/html?url=${encodeURIComponent(url)}`;
8
-
9
- fetch(endpoint)
10
- .then((res) => {
11
- if (!res.ok) {
12
- throw new Error(`HTTP error! status: ${res.status}`);
13
- }
14
- return res.text();
15
- })
16
- .then((text) => {
17
- const outputPath = path.join(__dirname, 'downloaded.html');
18
- fs.writeFileSync(outputPath, text, 'utf-8');
19
- console.log(`HTML saved to ${outputPath}`);
20
- })
21
- .catch((err) => {
22
- console.error('Error:', err);
23
- });